package awk.reflect;

import awk.loader.testModel.Hello;
import awk.reflect.annotation.Udf;
import awk.reflect.subclass.UdfFunction;
import awk.reflect.subclass._Concat_Ws;
import cn.hutool.core.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.reflections.Reflections;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/***
 * 该类工具适用于工厂模式通过反射机制，随机返回一个接口（或者父类）的实现类（或者子类）的对象
 **/
@Slf4j
public class ClassUtil1 {

    public static void main(String[] args) {
        //commons-lang3提供的ClassUtils，获取某个类继承的接口、抽象类、父类
        List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(UdfFunction.class);
        List<Class<?>> allInterfaces2 = ClassUtils.getAllInterfaces(_Concat_Ws.class);
        for (Class<?> allInterface : allInterfaces) {
            log.info("allInterface:{}",allInterface.getName());
        }
        for (Class<?> allInterface : allInterfaces2) {
            log.info("allInterface:{}",allInterface.getName());
        }

        //获取某个接口的实现类,方式1
        List<Class> allClassByInterface = getAllClassByInterface(UdfFunction.class);
        log.info("allClassByInterface" + allClassByInterface);

        //获取某个接口的实现类,方式2
        Set<Class<?>> instances = getInstances(Hello.class);
        for (Class<?> instance : instances) {
            System.out.println("instance " + instance.getName());
        }


        //GetClassByAnnotation org.reflections 反射工具包，指明扫描路径
        Reflections reflections = new Reflections("com.sia.acl.quartz.job");
        //获取带JobType注解的类
        Set<Class<?>> jobCLass = reflections.getTypesAnnotatedWith(Udf.class);
        for (Class<?> aClass : jobCLass) {
            //拿到对应的注解，获取自己想要的信息
            Udf annotation = aClass.getAnnotation(Udf.class);
        }
    }

    /**
     * 非spring环境，获取一个接口的所有实现类，或一个类的所有子类
     * spring环境下，spring框架本身提供的Springutils 也有类似的功能。
     * @param supClass
     * @return
     */
    private static Set<Class<?>> getInstances(Class supClass) {
        Set<Class<?>> classes = ClassUtil.scanPackage(ClassUtil.getPackage(supClass));
        return classes.stream().filter(sonClass -> ClassUtil.isAllAssignableFrom(new Class[]{supClass}, new Class[]{sonClass}) && sonClass != supClass).collect(Collectors.toSet());
    }

    /**
     * 获取某个接口的实现类
     * 给一个接口（或者父类），返回这个接口（或者父类）的所有实现类（或者子类）
     * （这些实现类（或者子类）的的包的路径必须要是接口（或者父类）的同一层或者下层）
     *
     * @param c
     * @return
     */
    @SuppressWarnings("unchecked")
    public static List<Class> getAllClassByInterface(Class c) {
        //返回结果
        List<Class> returnClassList = new ArrayList<>();
        //如果不是一个接口，则不做处理
        if (c.isInterface()) {
            //获得当前的包名
            try {
                //获得当前包下以及子包下的所有类
                ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                String packageName = c.getPackage().getName();
                String path = packageName.replace('.', '/');
                Enumeration<URL> resources = classLoader.getResources(path);
                ArrayList<Class> allClass = new ArrayList<Class>();
                while (resources.hasMoreElements()) {
                    URL resource = resources.nextElement();
                    allClass.addAll(findClasses(new File(resource.getFile()), packageName));
                }
                for (Class aClass : allClass) {
                    //在这里，既可以使用继承去实现，也可以使用注解，因为此处既扫描了继承，也扫描了注解
                    //判断是不是一个接口
                    if (c.isAssignableFrom(aClass) && !c.equals(aClass)) {
                        returnClassList.add(aClass);
                    }else if (aClass.isAnnotationPresent(Udf.class)){
                        //isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解；
                        //getAnnotation(Class<A> annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据；
                        //c.getAnnotation(Udf.class);

                        //getAnnotations()，该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组，
                        // 需要注意的是该数组的类型是Annotation类型，这个Annotation是一个来自于java.lang.annotation包的接口。
                        //Annotation[] annotations = c.getAnnotations();
                        returnClassList.add(aClass);
                    }
                }
            } catch (ClassNotFoundException | IOException e) {
                e.printStackTrace();
            }
        }
        return returnClassList;
    }


    /***
     * 找出目录directory下所有的class文件并以“完整包名”+“类名”的形式返回类名
     */
    @SuppressWarnings("unchecked")
    private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList<Class>();
        if (!directory.exists()) {
            return classes;
        }
        for (File file : directory.listFiles()) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }
}
